package davidlauzon.activerecord.connection;

import java.sql.Connection;
import java.sql.SQLException;

import davidlauzon.activerecord.recordset.AndroidSqliteRecordSet;
import davidlauzon.activerecord.recordset.RecordSet;
import davidlauzon.activerecord.visitor.SqliteSerializer;

import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteStatement;


/**
 * BUG FIX: error 'attempt to re-open an already-closed object: android.database.sqlite.SQLiteQuery'
 *    => need to open a new OpenHelper every time there is a query.
 *      http://stackoverflow.com/questions/1483629/exception-attempt-to-acquire-a-reference-on-a-close-sqliteclosable
 *      
 * @author david
 *
 */
public class AndroidSqliteAdapter extends ConnectionAdapter
{
    /*********************************************************************************************
     * CONSTANTS
     *********************************************************************************************/
    static public final String DRIVER = "sqlite";
    
    
    
    /*********************************************************************************************
     * VARIABLES
     *********************************************************************************************/
    private SQLiteDatabase          _db;
    private AndroidSqliteOpenHelper _dbHelper;
    
    
    
    /*********************************************************************************************
     * PUBLIC METHODS
     *********************************************************************************************/
    
    public AndroidSqliteAdapter(Context context, String dbName, int version) throws SQLException {
        super(dbName);
        setDriver(DRIVER);
        setDbName(dbName);
        setSerializer( new SqliteSerializer() );

        _dbHelper = new AndroidSqliteOpenHelper(context, getDbName(), version);
        
        connect();
        log("Connected to database '" + getDbName() + "'");
    }
    

    @Override
    protected void connect() throws SQLException {
        if (_db != null && _db.isOpen())
            log("Already connected to database '" + getDbName() + "'");
        else
            _db = _dbHelper.getWritableDatabase();
    }
    
    private SQLiteDatabase reconnectIfRequired() {
        if (_db == null || !_db.isOpen()) {
            try {
                connect();
                log("Reconnected to database '" + getDbName() + "'");
            } catch (SQLException e) {
                e.printStackTrace();
                log("FAILED to reconnect to database '" + getDbName() + "'");
            }
        }
        return _db;
    }
    
    @Override
    public void disconnect() {
        _db.close();
        _db = null;
        _dbHelper.close();
        log("Disconnected from database '" + getDbName() + "'");
    }
    
    @Override
    public int executeUpdate(String sql) throws SQLException
    {
        reconnectIfRequired();

        long startedAt = System.currentTimeMillis();
        SQLiteStatement statement = _db.compileStatement(sql);
        statement.execute();
        statement.close();
        logSQL(sql, startedAt);
        
        return (int) getNumAffectedRows();
    }
    
    @Override
    public long executeInsert(String sql) throws SQLException
    {
        reconnectIfRequired();
        
        long startedAt = System.currentTimeMillis();
        SQLiteStatement     statement = _db.compileStatement(sql);
        long lastInsertId = statement.executeInsert();
        statement.close();
        logSQL(sql, startedAt);
        
        return lastInsertId;
    }

    @Override
    public int executeAllBufferedUpdates() throws SQLException
    {
        reconnectIfRequired();
        
        int size = _bufferedUpdates.size();
        
        long startedAt = System.currentTimeMillis();
        int result = 0;
        
        for (int i =0; i< size; i++) {
            executeOneBufferedUpdate( _bufferedUpdates.get(i) );
        }
        
        logSQL( "[" + size + " queries] " + _bufferedUpdates.toString(), startedAt);
        
        resetBufferedUpdates();
        
        return result;
    }
        
    private void executeOneBufferedUpdate(String sql) throws IllegalStateException
    {
        reconnectIfRequired();
        
        SQLiteStatement statement = _db.compileStatement( sql );
        statement.execute();
        statement.close();
    }
    
    @Override
    public RecordSet executeQuery(String sql) throws SQLException
    {
        reconnectIfRequired();
        
        long startedAt = System.currentTimeMillis();
        AndroidSqliteRecordSet rs = createRecordSet( _db.rawQuery(sql, null), _db );
        logSQL(sql, startedAt);
        
        return rs;
    }

    @Override
    public long executeQueryForLong(String sql)
    {
        reconnectIfRequired();
        
        long startedAt  = System.currentTimeMillis();
        SQLiteStatement statement = _db.compileStatement(sql);
        long value    = statement.simpleQueryForLong();
        //statement.close();        // seems to cause a bug : "close was never explicitely called on database...."
        logSQL(sql, startedAt);
        
        return value;
    }
    
    /**
     * @return always 0 ??? is it because we create a new db every time ?
     */
    public long getNumAffectedRows()
    {
        reconnectIfRequired();
        
        SQLiteStatement        statement = _db.compileStatement("SELECT changes()");
        long numAffectedRows = statement.simpleQueryForLong();
        statement.close();
        
        return numAffectedRows;
    }
    
    @Override
    public long getLastInsertId() throws SQLException
    {
        reconnectIfRequired();
        
        SQLiteStatement     statement = _db.compileStatement("SELECT last_insert_rowid()");
        long lastInsertId = statement.simpleQueryForLong();
        statement.close();
        
        return lastInsertId;
    }


    @Override
    protected Connection getConnection() throws SQLException {
        String msg = getClass().getSimpleName() + 
            "::getConnection() is unimplemented - Android does not use this interface.";
        
        System.err.println("WARNING: " + msg );
        throw new SQLException("WARNING: " + msg );
    }
    

    protected AndroidSqliteRecordSet createRecordSet( Cursor cursor, SQLiteDatabase db ) {
        return new AndroidSqliteRecordSet(cursor, db);
    }
    
}